package com.nannybear.Model;

import android.os.Handler;

import java.util.ArrayList;
import java.util.Observer;

/**
 * \brief Manage the timeline
 * \author Audrey LOISEAU
 * \date 22-03-2015
 */
/********************************* COPYRIGHT *********************************
 * Copyright (c) 2015, Audrey LOISEAU and Benjamin LANCINOT.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *************************************************************************************/

public class Timeline extends Constant{
    /********** DECLARATIONS **********/
    // Variables
    private BabyState babyStateClass;
    private ArrayList<ProgressItem> mProgressItemsList;
    private final ArrayList<Observer> observers;
    private long oldTime;
    private long totalTime;
    private boolean isMyFirstValue;
    private int agitatedCounter;
    private double lastState;
    private double lastStateReceived;
    private int timeOfRadioButton;
    private int firstIndex[];
    private boolean offline;

    static private final double AGITATED = 3.0;
    static private final long THRESHOLD_TIME = 15000;
    static private final long COUNTER_TIME = THRESHOLD_TIME/1000;

    // Handler
    private final Handler myTimer = new Handler();

    /************ METHODS ************/
    /**
     * \brief Constructor
     */
    public Timeline(){
        this.observers = new ArrayList<>();
        init();
    }

    /**
     * \brief Add the class as observable
     * \param obs   The new observer
     */
    public void addObserver(Observer obs){
        observers.add(obs);
    }

    /**
     * \brief Delete the class as observable
     * \param obs   The observer to delete
     */
    public synchronized void deleteObserver(Observer obs){
        observers.remove(obs);
    }

    /**
     * \brief Notify the observers
     */
    public void notifyObservers(){
         for (int i = 0; i < observers.size(); i++) {
            observers.get(i).update(this, this.mProgressItemsList);
        }
    }

    /**
     * \brief Initialization of the values
     */
    private void init(){
        mProgressItemsList = new ArrayList<>();
        totalTime = 0;
        isMyFirstValue = true;
        lastState=0;
        timeOfRadioButton =0;
        firstIndex = new int[2];
        firstIndex[0]=0;
        firstIndex[1]=0;
        babyStateClass = new BabyState();
    }

    /**
     * \brief Reception of a new value and get the time if it's the first value
     * \param data  The new data received
     */
    public void update(String data){
        if(isMyFirstValue){
            oldTime = System.currentTimeMillis();
            isMyFirstValue=false;
        }
        myTimer.removeCallbacksAndMessages(null);
        calculateArrayList(true, Double.parseDouble(data));
    }

    /**
     * \brief Timer to update the view every second
     */
    private void updateByTimer(){
        final Runnable timer = new Runnable() {
            @Override
            public void run() {
                calculateArrayList(false, 0);
            }
        };
        myTimer.postDelayed(timer, 1000);
    }

    /**
     * Return the real state considering the agitated state
     * \param BbStateReceived   The state received
     * \return The real baby state
     */
    private double giveMeTrueBBState(double BbStateReceived){
        if (agitatedCounter<COUNTER_TIME){
            // If it is the first state received
            if (lastState == 0){
                return BbStateReceived;
            }
            else{
                return AGITATED;
            }
        }
        else{
            return BbStateReceived;
        }
    }

    /**
     * \brief Give the true index of the array list to display the good timeline depending on which radio button is chosen
     * \return The index of the first state to displayed
     */
    private int[] giveMeTrueIndex(){
        int value[]= new int[2];

        // Radio button = Overview
        if (timeOfRadioButton == 0){
            value[0]=0;     // Represents the index
            value[1]=0;     // Represents the time to add to complete the time to see
            return value;
        }
        else {
            int trueIndex = mProgressItemsList.size() -1;
            int timeCounter = 0;
            while (timeCounter < timeOfRadioButton){
                if (trueIndex >= 0) {
                    timeCounter += (int) mProgressItemsList.get(trueIndex).time ;
                    if (timeCounter < timeOfRadioButton) {
                        trueIndex--;
                    }
                }else{
                    timeCounter = timeOfRadioButton;
                }
            }
            value[0] = trueIndex;
            value[1] = timeCounter - timeOfRadioButton;
            return value;
        }
    }

    /**
     * \brief Update the progressItem of the array list
     * \param fromUpdate    If the update comes from the pattern observer
     * \param bbState   The state of the baby
     */
    private void calculateArrayList(boolean fromUpdate, double bbState) {
        long interval = System.currentTimeMillis() - oldTime;

        // If it is not the first state
        if (bbState != 0) {
            lastStateReceived = bbState;
        }
        if(bbState != -1) {
            bbState = giveMeTrueBBState(bbState);
            if(bbState != 0){
                offline = false;
            }
        }
        // -1 represents a connection lost, creation of a new square
        if(bbState == -1){
            offline = true;
            ProgressItem progressItem = new ProgressItem();
            progressItem.state = bbState;
            progressItem.time = 0;
            mProgressItemsList.add(progressItem);
        }
        agitatedCounter++;

        // If it is a new value received
        if (fromUpdate && !offline) {
            agitatedCounter = 0;
            // If the last and the new state is agitated add the interval time on the last item
            if (lastState == AGITATED && bbState == AGITATED) {
                mProgressItemsList.get(mProgressItemsList.size() - 1).time += interval;
            }
            // If new state is agitated and the last state wasn't and the time of the last item was less than THRESHOLD_TIME
            else if (lastState != AGITATED && bbState == AGITATED && mProgressItemsList.get(mProgressItemsList.size() - 1).time < THRESHOLD_TIME) {
                if (mProgressItemsList.size() > 1) {
                    // Check the before last item to not create two rectangle agitated
                    // If it was agitated, remove it and take is time
                    if (mProgressItemsList.get(mProgressItemsList.size() - 2).state == AGITATED) {
                        mProgressItemsList.get(mProgressItemsList.size() - 2).isDoubleAgitated = true;
                        mProgressItemsList.get(mProgressItemsList.size() - 2).time += mProgressItemsList.get(mProgressItemsList.size() - 1).time;
                        mProgressItemsList.remove(mProgressItemsList.size() - 1);
                    } else {
                        mProgressItemsList.get(mProgressItemsList.size() - 1).time += interval;
                        mProgressItemsList.get(mProgressItemsList.size() - 1).state = AGITATED;
                    }
                } else {
                    mProgressItemsList.get(0).time += interval;
                    mProgressItemsList.get(0).isDoubleAgitated = true;
                    mProgressItemsList.get(0).state = AGITATED;
                }

            }
            // Add a new rectangle
            else {
                ProgressItem progressItem = new ProgressItem();
                progressItem.state = bbState;
                progressItem.time = 0;
                mProgressItemsList.add(progressItem);
            }
            lastState = bbState;
        }

        // If it comes from the timer
        if(!fromUpdate && !offline) {
            // If the time is more than THRESHOLD_TIME and the last state was agitated, add a new rectangle
            if (agitatedCounter >= COUNTER_TIME && lastState == AGITATED) {
                ProgressItem progressItem = new ProgressItem();
                progressItem.state = lastStateReceived;
                progressItem.time = 0;
                mProgressItemsList.add(progressItem);
                lastState = lastStateReceived;
                agitatedCounter = 0;
            }
            // Update the time
            else {
                mProgressItemsList.get(mProgressItemsList.size() - 1).time += interval;
            }
        }
        else{
            mProgressItemsList.get(mProgressItemsList.size() - 1).time += interval;
        }
        totalTime = totalTime + interval;
        oldTime = System.currentTimeMillis();

        // Calculates the percentage of each item
        calculatePercentage();
    }

    /**
     * \brief Calculates the percentage of each item
     */
    private void calculatePercentage(){
        firstIndex = giveMeTrueIndex();

        float totTime;
        if(mProgressItemsList.size() > 1 ) {
            // If radio button = overview
            if (timeOfRadioButton == 0 || firstIndex[1] <= 0){
                totTime = totalTime;
                for (int i = 0; i < mProgressItemsList.size(); i++) {
                    mProgressItemsList.get(i).percentage =  (mProgressItemsList.get(i).time / totTime) * 100;
                }
            }
            else{
                for (int i =  0; i < firstIndex[0]; i++) {
                    mProgressItemsList.get(i).percentage = 0;
                }

                totTime = timeOfRadioButton;
                mProgressItemsList.get(firstIndex[0]).percentage = ((mProgressItemsList.get(firstIndex[0]).time - firstIndex[1]) / totTime) * 100;

                for (int i = firstIndex[0]+1; i < mProgressItemsList.size(); i++) {
                    mProgressItemsList.get(i).percentage = (mProgressItemsList.get(i).time / totTime) * 100;
                }
            }
        }
        // If it is the first and the last item put its percentage at 100%
        else {
            mProgressItemsList.get(0).percentage =100;
        }
        this.getBabyStateClass().changeState(mProgressItemsList.get(mProgressItemsList.size()-1));
        notifyObservers();
        updateByTimer();
    }

    /**
     * \brief Time changed by the progress bar
     * \param progressValue The value on the progress bar
     */
    public void customTime(int progressValue){
        int temp =(int) ((totalTime * (progressValue / 100.0)) / 1000);
        if(temp <= 1){
            temp = 1;
        }
        timeOfRadioButton = temp*1000;
    }

    /*********** ACCESSORS ***********/
    /**
     * \brief Get method for babyStateClass class
     */
    public BabyState getBabyStateClass() {
        return babyStateClass;
    }

    /**
     * \brief Get method for list of state
     */
    public ArrayList<ProgressItem> getmProgressItemsList() {
        return mProgressItemsList;
    }

    /**
     * \brief Set method for the time to see (determined by the radio button selected)
     */
    public void setTimeOfRadioButton(int timeOfRadioButton) {
        this.timeOfRadioButton = timeOfRadioButton;
    }
}